package yawning.mew.sim;

import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import yawning.mew.MewProperty;
import yawning.mew.MewView;
import yawning.mew.PropertiesBase;
import yawning.mew.Report;
import yawning.mew.character.Toon;
import yawning.mew.target.Target;

public class SimulationManager {
    private Report mAccumulatedReport;
    private int mTotalJobs;
    private int mCompletedJobs = 0;
    private int mSentJobs = 0;
    private int mSeed = 0;
    private PropertiesBase mConfig;
	private Simulation mSim;
	private MewView mMewView;
	private Toon mToon;
	private Target mTarget;

	// Thread pool goo.
	private Lock mLock = new ReentrantLock();
	private CountDownLatch mLatch;
	private volatile boolean mAborted = false;
	private boolean mLoggedFatalError = false;
	private boolean mGenerateLog = false;

	public static class Property {
		public static final MewProperty SIMULATION_ITERATIONS = new MewProperty("Simulator.Iterations", 1);
		public static final MewProperty SIMULATION_THREADS = new MewProperty("Simulator.Threads", 0);
		public static final MewProperty SIMULATION_SEED = new MewProperty("Simulator.Seed", 0);
		public static final MewProperty SIMULATION_PRNG = new MewProperty("Simulator.PRNG", "org.apache.commons.math.random.Well19937c");
		public static final MewProperty SIMULATION_DURATION = new MewProperty("Simulator.Duration", 300);
		public static final MewProperty SIMULATION_DURATION_JITTER = new MewProperty("Simulator.DurationJitter", 0);	// +- Jitter %
		public static final MewProperty SIMULATION_HIGHRES_HZ = new MewProperty("Simulator.HighResHz", false);
		public static final MewProperty SIMULATION_GENERATE_LOG = new MewProperty("Simulator.GenerateLog", false);	// Generate a combat log
		public static final MewProperty SIMULATION_STRATEGY = new MewProperty("Simulator.Strategy", "");				// User decision strategy to use
		public static final MewProperty SIMULATION_STRATEGY_ARG = new MewProperty("Simulator.StrategyArg", "");		// Argument to pass to the onSimulationStart hook
		public static final MewProperty SIMULATION_PREPOTION = new MewProperty("Simulator.PrePotion", false);
	}

	public SimulationManager(MewView v) {
		mMewView = v;
	}

    public class Worker extends Thread {
    	@Override
    	public void run() {
    		try {
        		int iteration;
	    		while ((iteration = getJob()) < mTotalJobs && !mAborted) {
    				SimulationEngine su = new SimulationEngine(mSim, mToon, mTarget, mConfig, mMewView, mSeed + iteration, (mGenerateLog && iteration == 0));
    				onJobCompleted(su.run());
	    		}
			} catch(RuntimeException e) {
				OutputStream buffer = new ByteArrayOutputStream();
				e.printStackTrace(new PrintStream(buffer));
				logFatalError(buffer.toString());
				abort();
    		} finally {
    			mLatch.countDown();
    		}
    	}
    }

    synchronized private void logFatalError(String s) {
    	// Chances are if one instance of the calculations terminates abruptly with a
    	// fatal error, they all will with an identical one.  Prevent mass error spam.
    	if (mLoggedFatalError == false) {
    		mLoggedFatalError = true;
    		mMewView.onError(s);
    	}
    }

    synchronized private int getJob() {
//    	mMewView.onLog("Dispatching job #" + mSentJobs);
    	return mSentJobs++;
    }

    synchronized private void onJobCompleted(Report r) {
   		mAccumulatedReport.accumulate(r);
    	mCompletedJobs++;
//    	mMewView.onLog("Completed job #" + mCompletedJobs);
    	mMewView.onIteration(mCompletedJobs, mTotalJobs);
    }

    public void abort() {
    	mAborted = true;
    }

	public Report simulate(Toon toon, Target target, PropertiesBase config, Simulation sim) {
		if (!mLock.tryLock()) throw new RuntimeException("Already simulating with this instance.");

		mSim = sim;
		mTotalJobs = config.getInt(Property.SIMULATION_ITERATIONS);
		mSeed = config.getInt(Property.SIMULATION_SEED);
		mSentJobs = mCompletedJobs = 0;
		mConfig = config;
		mMewView.onIteration(0, mTotalJobs);
		mAccumulatedReport = mSim.getBaseReport();
		mAborted = false;
		mLoggedFatalError = false;
		mToon = toon;
		mTarget = target;
		mGenerateLog = config.getBool(Property.SIMULATION_GENERATE_LOG);

		int threads = config.getInt(Property.SIMULATION_THREADS);
		if (threads < 1) threads = Runtime.getRuntime().availableProcessors();
		mLatch = new CountDownLatch(threads);

		// Dispatch our threads.
		for (int i = 0; i < threads; i++) {
			Worker w = new Worker();
			w.start();
		}

		try {
			mLatch.await();
		} catch (InterruptedException e) {
			// Abnormal termination, I should do something better here.
			throw new RuntimeException(e);
		} finally {
			mLock.unlock();
		}

		return mAccumulatedReport;
	}
}
